 <?php

class edgeLabel extends GtkLabel {

	public function __construct($text = '',$angle = 0) {
		parent::__construct();
		
		$this->set_use_markup(true);
		$this->set_markup($text);
		$this->set_alignment(0.0,0.5);
		$this->set_angle($angle);
	
		$this->show_all();
		return;
	}
}

class edgeMenu extends GtkEventBox {

	private $oldtext;

	public function __construct($label,$item) {
		parent::__construct();
		$this->label = new GtkLabel($label);

		$this->menu = new GtkMenu;
		$this->menuitem = array();
		
		$this->connect_simple(
			'button-press-event',
			array($this->menu,'popup')
		); $this->connect_simple(
			'enter-notify-event',
			array($this,'on_mouse_over')		
		); $this->connect_simple(
			'leave-notify-event',
			array($this,'on_mouse_out')		
		);
		
		foreach($item as $key => $info) {
			switch($info[0]) {
				case('title'): {
					$this->menuitem[$key] = new GtkMenuItem($info[1]);
					$this->menuitem[$key]->set_sensitive(false);
					if(!$info[2]) {
						$this->menu->add($this->menuitem[$key]);
					}
					break;
				}
				case('item'): {
					$this->menuitem[$key] = new GtkMenuItem($info[1]);
					if(!$info[2]) {
						$this->menu->add($this->menuitem[$key]);
					}
					if(array_key_exists(3,$info) && $info[3]) {
						$this->menuitem[$key]->connect_simple('activate',$info[3]);
					}
					break;
				}
				case('bar'): {
					$this->menuitem[$key] = new GtkSeparatorMenuItem;
					if(!$info[1]) {
						$this->menu->add($this->menuitem[$key]);
					}
					break;
				}
			}
		}

		$this->add($this->label);
		$this->menu->show_all();
		$this->show_all();
		return;
	}
	
	public function on_mouse_over() {
		$this->label->set_markup(sprintf(
			'<span color="#ff8a00">%s</span>',
			($this->oldtext = $this->label->get_text())		
		));
		return;
	}
	
	public function on_mouse_out() {
		$this->label->set_markup($this->oldtext);
		return;
	}

}

class edgeStatusBar extends GtkVBox {
	
	public function __construct($parent) {
		parent::__construct();
		
		$this->hsep = new GtkHSeparator;
		$this->vsep = new GtkVSeparator;
		$this->tsep = new GtkVSeparator;
		$this->hbox = new GtkHBox;
		$this->message = new GtkLabel;
		$this->time = new GtkLabel;

		$this->opt = new edgeMenu(
			'Options...',
			array(
				'title' => array('title',edge::$title,null),
				'bar03' => array('bar',null),
				'managestreams' => array('item','Manage Streams...',null),
				'options' => array('item','More Options...',null,array($parent,'on_options')),
				'bar02' => array('bar',null),
				'quit' => array('item','Quit',null,array($parent,'on_quit'))			
			)
		);		

		$this->message->set_ellipsize(Pango::ELLIPSIZE_END);
		$this->message->set_justify(Gtk::JUSTIFY_LEFT);
		$this->message->set_alignment(0.0,0.5);

		$this->hbox->pack_start($this->opt,false,false,3);
		$this->hbox->pack_start($this->vsep,false,false,3);
		$this->hbox->pack_start($this->time,false,false,3);
		$this->hbox->pack_start($this->tsep,false,false,3);
		$this->hbox->pack_start($this->message,true,true,3);
		
		$this->pack_start($this->hsep,false,false,0);
		$this->pack_start($this->hbox,true,true,1);
		$this->show_all();
		return;edge::$stream = $this->streamselect->current();
	}
	
	public function set($text1,$text2) {
		$this->time->set_markup($text1);
		$this->message->set_markup($text2);
		return;
	}
}

class edgeCacheBar extends GtkProgressBar {

	public function update($showtext = true) {
		if(edge::$state == edge::STATE_PLAYING) {
			if(edge::$bufferpre > edge::$bufferpremax) { edge::$bufferpre = edge::$bufferpremax; }		
			$percent = round((edge::$bufferpre / edge::$bufferpremax),3);
		} else {
			$percent = round((edge::$bufferpre / edge::$buffermax),3);
		}

		if($percent >= 0.95) { $percent = 1; }
		$this->set_fraction($percent);

		if($showtext === TRUE) {
			$this->set_text(sprintf(
				"Buffer: %d%%",
				($percent * 100)
			));
		} else if($showtext) {
			$this->set_text(sprintf(
				"Buffer: %d%%\n%s",
				($percent * 100),
				$showtext
			));		
		} else {
			$this->set_text('');		
		}
		return;
	}
	
}

class edgeTypeSelect extends GtkComboBox {

	public $active;

	public function __construct() {
		parent::__construct();

		$this->data = new GtkListStore(GObject::TYPE_STRING,GObject::TYPE_STRING);
		$this->render = new GtkCellRendererText;
		$this->set_model($this->data);
		$this->pack_start($this->render);
		$this->set_attributes($this->render,'text',0);
		
		foreach(edge::$type as $key => $type) {
			$this->data->append(array($type,$key));
		}

		$this->show_all();
		return;
	}
	
	public function update() {
		if(edge::active()) {
			if($this->active != $this->get_active()) {
				$this->set_active($this->active);
			} return false;
		} else {
			$this->active = $this->get_active();
			return true;
		}
	}
}

class edgeStreamSelect extends GtkComboBox {

	public $active;	
	
	public function __construct() {
		parent::__construct();

		$this->data = new GtkListStore(GObject::TYPE_STRING,GObject::TYPE_STRING);
		$this->render = new GtkCellRendererText;
		$this->set_model($this->data);
		$this->pack_start($this->render);
		$this->set_attributes($this->render,'text',0);

		$this->show_all();	
		return;
	}

	public function update() {
		if(edge::active()) {
			if($this->active != $this->get_active()) {
				$this->set_active($this->active);
			} return false;
		} else {
			$this->active = $this->get_active();
			edge::$stream = $this->current();
			return true;
		}
	}

	public function populate($type) {

		if(edge::idle()) {
			$this->type = $type->data->get_value(
				$this->iter = $type->get_active_iter(),
				1
			);
			
			edge::$stype = $this->type;
		
			$this->data->clear();
			foreach(edge::$list[$this->type] as $genre => $url) {
				$this->data->append(array($genre,$url));
			}
			
			$this->set_active(0);
		}

		return;
	}

	public function current() {
		return $this->data->get_value($this->get_active_iter(),0);
	}
	
	public function current_url() {
		return $this->data->get_value($this->get_active_iter(),1);
	}
	
}

class edgeVolumeButton extends GtkVolumeButton {

	public function __construct() {
		parent::__construct();
		
		preg_match(
			'/\[(\d+)\%\]/',
			shell_exec('amixer get Master | grep "Mono: Playback"'),
			$match
		);
		
 		$this->connect_simple(
			'value-changed',
			array($this,'on_change')		
		);
		
		$this->set_value($match[1] / 100);
		edge::update_ui();	
		
		$this->show_all();
		return;
	}
	
	public function on_change() {
		$volume = $this->get_value() * 100;
		system("amixer -q set Master {$volume}%");	
		return;
	}

}

class edgeWindow extends GtkWindow {

	private $playerbuffer;
	private $pp;
	public $pipe;

	public function __construct() {
		parent::__construct();

		$this->vbox = new GtkVBox;
		$this->bbox = new GtkHBox;
		$this->tbox = new GtkHBox;
		$this->sbox = new GtkHBox;
		$this->cache = new edgeCacheBar;
		$this->play = new GtkButton;
		$this->typeselect = new edgeTypeSelect;
		$this->streamselect = new edgeStreamSelect;
		$this->status = new edgeStatusBar($this);
		$this->volume = new edgeVolumeButton;
		$this->typeadd = new GtkButton(' + ');
		$this->streamadd = new GtkButton(' + ');

		$this->connect_simple(
			'delete-event',
			array($this,'on_quit')
		); $this->typeselect->connect_simple(
			'changed',
			array($this->typeselect,'update')
		); $this->typeselect->connect_after(
			'changed',
			array($this->streamselect,'populate')
		); $this->streamselect->connect_simple(
			'changed',
			array($this->streamselect,'update')
		); $this->play->connect_simple(
			'clicked',
			array($this,'on_play')		
		); $this->connect(
			'window-state-event',
			array($this,'on_state_event')		
		);
		
		$this->set_title(edge::$title);
		$this->set_position(Gtk::WIN_POS_CENTER);
		$this->set_icon_from_file(EXECROOT.'/share/edgecast/icon.png');
		$this->cache->set_size_request(256,-1);
		$this->play->set_image(GtkImage::new_from_stock(Gtk::STOCK_MEDIA_PLAY,Gtk::ICON_SIZE_BUTTON));
		$this->status->set('Stopped',edge::$title);
		
		$this->typeselect->set_active(edge::$inttype);
		$this->streamselect->set_active(edge::$intstream);

		$this->tbox->pack_start($this->typeselect,true,true,3);
		$this->sbox->pack_start($this->streamselect,true,true,3);
		$this->bbox->pack_start($this->play,false,false,3);
		$this->bbox->pack_start($this->cache,true,true,3);
		$this->bbox->pack_start($this->volume,true,true,3);
		$this->vbox->pack_start($this->tbox,true,true,3);
		$this->vbox->pack_start($this->sbox,true,true,3);
		$this->vbox->pack_start($this->bbox,true,true,3);
		$this->vbox->pack_start($this->status,false,false,0);
		$this->add($this->vbox);
		$this->show_all();
		return;
	}
	
	public function on_state_event($w,$e) {
		if($e->new_window_state & Gdk::WINDOW_STATE_ICONIFIED) {
			if(edge::$usetray && edge::$t->alive) {
				$this->set_skip_taskbar_hint(true);
			}
		} else {
			$this->set_skip_taskbar_hint(false);
		}
		return;
	}	
	
	public function on_options() {
		$dialog = new edgeOptionsDialog($this);
		$dialog->destroy();
		unset($dialog);
		return;		
	}	
	
	public function on_play() {

		if($this->pp) {
			proc_terminate($this->pp);
			$this->pp = null;
		}

		if(edge::$state == edge::STATE_STOPPED) {
	
			$stream = edge::$stream = $this->streamselect->current();
			$url = edge::$url = $this->streamselect->current_url();
			
			edge::clear_title_buffer();
			edge::$inttype = $this->typeselect->get_active();
			edge::$intstream = $this->streamselect->get_active();
			edge::write_config();
		
			$this->pp = proc_open(
				sprintf(
					'mplayer "%s" -cache %d -cache-min %.1f',
					$url,
					edge::$cachesize,
					edge::$cachefill
				),
				array(array('pty'),array('pty'),array('pty')),
				$this->pipe, null, null
			);			

			stream_set_blocking($this->pipe[1],false);
			
			if($this->pp) {
				edge::$state = edge::STATE_CONNECTING;
				edge::$inittime = time();
				
				$this->watch = Gtk::io_add_watch($this->pipe[1],GObject::IO_IN,array($this,'on_player_output'));

				$this->status->set('Connecting',edge::$stream);
				$this->play->set_image(GtkImage::new_from_stock(Gtk::STOCK_MEDIA_STOP,Gtk::ICON_SIZE_BUTTON));
				edge::$t->set_tooltip('Connecting to '.edge::$stream.'...');
				edge::$t->on_automatic_display();
			}	
		}
		
		else {
			edge::$state = edge::STATE_STOPPED;
			edge::$bufferpre = 0;

			Gtk::input_remove($this->watch);
			$this->watch = null;
			
			$this->cache->update(false);
			$this->status->set('Stopped',edge::$title);
			$this->play->set_image(GtkImage::new_from_stock(Gtk::STOCK_MEDIA_PLAY,Gtk::ICON_SIZE_BUTTON));

			edge::$t->set_tooltip(null);
			edge::$t->set_blinking(false);
			edge::$t->on_automatic_display();
		}		
		
		return;
	}
	
	public function on_player_output($stream,$cond) {
		
		$now = time();		
		
		while($dump = fread($stream,1024)) {
			$this->playerbuffer .= $dump;
		}

		while(preg_match('/^(.*?)\n/',$this->playerbuffer,$input)) {

			$string = $input[1];
			$this->playerbuffer = preg_replace(
				sprintf('/^%s/',preg_quote($input[0],'/')),
				'',
				$this->playerbuffer
			);

			if(edge::$state == edge::STATE_CONNECTING) {
				if(preg_match('/^Cache size set to (\d+) KBytes/',$string,$match)) {
					edge::$state = edge::STATE_BUFFERING;
					edge::$buffermax = ((int)$match[1]) * 1024;
					edge::$t->on_automatic_display();
				}
			}

			if(edge::$state == edge::STATE_PLAYING) {
				if(preg_match('/^ICY Info: StreamTitle=\'(.*?)\';/',$string,$match)) {
					edge::queue_title($match[1]);
				}
			}
		}

		while(preg_match('/^(.*?)\r/',$this->playerbuffer,$input)) {

			$string = $input[1];
			$this->playerbuffer = preg_replace(
				sprintf('/^%s/',preg_quote($input[0],'/')),
				'',
				$this->playerbuffer
			);

			if(edge::$state == edge::STATE_BUFFERING) {
				if(preg_match('/^Cache fill: .+? \((\d+) bytes\)/',$string,$match)) {
					edge::$bufferpre = (int)$match[1];
					$this->cache->update();
					$this->status->set('Buffering',edge::$stream);
					edge::$t->set_tooltip('Buffering '.edge::$stream.'...');
				}
				
				if(substr($string,0,2) == 'A:') {
					edge::$state = edge::STATE_PLAYING;
					edge::$starttime = $now;
					edge::$distance = edge::$starttime - edge::$inittime;
					edge::$bufferpremax = edge::$bufferpre;
					$this->cache->update();
				}					
			}
			
			if(edge::$state == edge::STATE_PLAYING) {
				if(($now - edge::$lasttime) >= 1) {
					if(preg_match('/^A:\s*([\d]+)\.\d \(\s*([\d:\.]+)\.\d+\) of 0\.0 \(unknown\)\s*[\d\.]+% ([\d]+)%/',$string,$match)) {

						//. first idea for easily handling a buffer failure. also need
						//. a bit for failed connections somewhere (probably not here)
						if((int)$match[3] === 0) {
							$this->on_play();
							edge::update_ui();
							$this->on_play();
							edge::update_ui();
							return;
						}

						edge::$bufferpre = edge::$buffermax * (((int)$match[3]) / 100);
						$this->cache->update();
				
						edge::$currenttitle = str_replace('&','&amp;',edge::get_title());
						if(!edge::$currenttitle) { edge::$currenttitle = 'Unknown Track'; }
						
						if(edge::$currenttitle != edge::$lasttitle) {
							edge::$lasttitle = edge::$currenttitle;
							
							if(edge::$songtraypop) {
								edge::$t->on_automatic_display();
							}
						}
					
						$this->status->set(
							sprintf("%s, %s%%",$match[2],$match[3]),
							edge::$currenttitle
						);
						
						if(edge::$t->alive) {
							edge::$t->set_tooltip(sprintf(
								'%s: %s',
								edge::$stream,
								edge::$currenttitle
							));
							
							if(edge::$bufferpre / edge::$bufferpremax <= edge::$bufferlowthresh) {
								edge::$t->set_blinking(true);
							} else {
								edge::$t->set_blinking(false);
							}
						}
					}
	
					edge::$lasttime = $now;
				}
			}

		}

		return true;
	}
	
	public function on_quit() {

		if($this->pp) {
			proc_terminate($this->pp);
			$this->pp = null;
		}	
	
		$this->hide();

		edge::write_config();		
		
		Gtk::main_quit();
		return;
	}
	
	public function is_iconified() {
		if($this->window->get_state() & Gdk::WINDOW_STATE_ICONIFIED) {
			return true;
		} else {
			return false;
		}
	}
}

?>